#include <ani.h>
#include <array>
#include <iostream>
#include <vector>

std::string ANIUtils_ANIStringToStdString(ani_env *env, ani_string ani_str) {
  ani_size strSize;
  env->String_GetUTF8Size(ani_str, &strSize);

  std::vector<char> buffer(strSize + 1); // +1 for null terminator
  char *utf8_buffer = buffer.data();

  // String_GetUTF8 Supportted by
  // https://gitee.com/openharmony/arkcompiler_runtime_core/pulls/3416
  ani_size bytes_written = 0;
  env->String_GetUTF8(ani_str, utf8_buffer, strSize + 1, &bytes_written);

  utf8_buffer[bytes_written] = '\0';
  std::string content = std::string(utf8_buffer);
  return content;
}

template <typename F>
static bool forEachMapEntry(ani_env *env, ani_object map_object, F &&callback) {
  ani_ref keys;
  if (ANI_OK !=
      env->Object_CallMethodByName_Ref(map_object, "keys", nullptr, &keys)) {
    std::cerr << "Failed to get keys iterator" << std::endl;
    return false;
  }

  bool success = true;
  while (true) {
    ani_ref next;
    ani_boolean done;

    // Get next key
    if (ANI_OK != env->Object_CallMethodByName_Ref(
                      static_cast<ani_object>(keys), "next", nullptr, &next)) {
      std::cerr << "Failed to get next key" << std::endl;
      success = false;
      break;
    }

    // Check if done
    if (ANI_OK != env->Object_GetFieldByName_Boolean(
                      static_cast<ani_object>(next), "done", &done)) {
      std::cerr << "Failed to check iterator done" << std::endl;
      success = false;
      break;
    }
    if (done)
      break;

    // Get key
    ani_ref key_value;
    if (ANI_OK != env->Object_GetFieldByName_Ref(static_cast<ani_object>(next),
                                                 "value", &key_value)) {
      std::cerr << "Failed to get key value" << std::endl;
      success = false;
      break;
    }

    // Get value
    ani_ref value_obj;
    if (ANI_OK != env->Object_CallMethodByName_Ref(map_object, "$_get", nullptr,
                                                   &value_obj, key_value)) {
      std::cerr << "Failed to get value for key" << std::endl;
      success = false;
      break;
    }

    // Process key-value pair
    if (!callback(key_value, value_obj)) {
      success = false;
      break;
    }
  }

  // References are managed automatically in ANI
  return success;
}

class ANIIntanceHelper {
public:
  ANIIntanceHelper(ani_env *env) { _env = env; };
  bool Init() {

    if (ANI_OK != _env->FindClass("std.core.String", &_stringType)) {
      std::cerr << "FindClass std.core.String FAILD" << std::endl;
      return false;
    }
    if (ANI_OK != _env->FindClass("escompat.Record", &_recordType)) {
      std::cerr << "FindClass escompat.Record FAILD" << std::endl;
      return false;
    }
    if (ANI_OK != _env->FindClass("std.core.Numeric", &_numberType)) {
      std::cerr << "FindClass std.core.Numeric FAILD" << std::endl;
      return false;
    }
    _inited = true;
    return true;
  }
  bool isString(ani_object obj) {
    if (!_inited) {
      return false;
    }
    ani_boolean is_string;
    if (ANI_OK != _env->Object_InstanceOf(static_cast<ani_object>(obj),
                                          static_cast<ani_type>(_stringType),
                                          &is_string)) {
      std::cerr << "Call Object_InstanceOf Fail" << std::endl;
      return false;
    }
    return (bool)is_string;
  }
  bool isRecord(ani_object obj) {
    if (!_inited) {
      return false;
    }
    ani_boolean is_record;
    if (ANI_OK != _env->Object_InstanceOf(static_cast<ani_object>(obj),
                                          static_cast<ani_type>(_recordType),
                                          &is_record)) {
      std::cerr << "Call Object_InstanceOf Fail" << std::endl;
      return false;
    }
    return (bool)is_record;
  }
  bool isNumber(ani_object obj) {
    if (!_inited) {
      return false;
    }
    ani_boolean is_number;
    if (ANI_OK != _env->Object_InstanceOf(static_cast<ani_object>(obj),
                                          static_cast<ani_type>(_numberType),
                                          &is_number)) {
      std::cerr << "Call Object_InstanceOf Fail" << std::endl;
      return false;
    }
    return (bool)is_number;
  }

  bool isArray(ani_object obj) {
    if (!_inited) {
      return false;
    }
    ani_size arrayLength;
    if (ANI_OK !=
        _env->Array_GetLength(static_cast<ani_array>(obj), &arrayLength)) {
      return false;
    }
    return true;
  }

private:
  ani_class _stringType;
  ani_class _recordType;
  ani_class _numberType;
  ani_env *_env;
  bool _inited = false;
  // ani_type arrayType;
};

static bool ProcMapEntry(ani_env *env, ani_ref key, ani_ref value,
                         ANIIntanceHelper &ih);
bool ProcMapKey(ani_env *env, ani_ref value, ANIIntanceHelper &ih) {
  if (ih.isNumber(static_cast<ani_object>(value))) {
    ani_double numberValue;
    if (ANI_OK != env->Object_CallMethodByName_Double(
                      static_cast<ani_object>(value), "doubleValue", nullptr,
                      &numberValue)) {
      std::cerr << "Object_CallMethodByName_Double FAILD" << std::endl;
      return false;
    }
    std::cout << "value: " << numberValue << std::endl;
    return true;
  } else if (ih.isString(static_cast<ani_object>(value))) {
    auto value_string =
        ANIUtils_ANIStringToStdString(env, static_cast<ani_string>(value));
    std::cout << "value: " << value_string << std::endl;
    return true;
  } else if (ih.isRecord(static_cast<ani_object>(value))) {
    return forEachMapEntry(env, static_cast<ani_object>(value),
                           [env, &ih](ani_ref key, ani_ref value) -> bool {
                             return ProcMapEntry(env, key, value, ih);
                           });
  } else if (ih.isArray(static_cast<ani_object>(value))) {
    ani_size arrayLength;
    if (ANI_OK !=
        env->Array_GetLength(static_cast<ani_array>(value), &arrayLength)) {
        return false;
    }
    for (ani_size i = 0; i < arrayLength; i++) {
      ani_ref array_entry;
      auto status = env->Array_Get_Ref(static_cast<ani_array_ref>(value), i,
                                       &array_entry);
      if (status != ANI_OK) {
        std::cerr << "Array_Get_Ref FAILED index: " << i << std::endl;
        return false;
      }
      auto ret = ProcMapKey(env, array_entry, ih);
      if (!ret) {
        return false;
      }
    }
  }
  return true;
}

bool ProcMapEntry(ani_env *env, ani_ref key, ani_ref value,
                  ANIIntanceHelper &ih) {
  // 判断 key 是否是 string
  if (ih.isString(static_cast<ani_object>(key))) {
    auto key_string =
        ANIUtils_ANIStringToStdString(env, static_cast<ani_string>(key));
    std::cout << "key: " << key_string << std::endl;
  }
  return ProcMapKey(env, value, ih);
}

static void ProcessWantPara(ani_env *env, ani_object object,
                            ani_object map_object) {
  std::cout << "Start" << std::endl;
  ANIIntanceHelper ih(env);
  if (!ih.Init()) {
    std::cout << "ih init fail" << std::endl;
    return;
  }

  forEachMapEntry(env, map_object,
                  [env, &ih](ani_ref key, ani_ref value) -> bool {
                    return ProcMapEntry(env, key, value, ih);
                  });

  std::cout << "walkMap Done" << std::endl;
}

ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result) {
  ani_env *env;
  if (ANI_OK != vm->GetEnv(ANI_VERSION_1, &env)) {
    std::cerr << "Unsupported ANI_VERSION_1" << std::endl;
    return ANI_ERROR;
  }

  // 当前文件全局的 Function 实际时 ani_wantpara.ETSGLOBAL 这个class下的一个方法
  static const char *className = "ani_wantpara.ETSGLOBAL";

  ani_class cls;
  if (ANI_OK != env->FindClass(className, &cls)) {
    std::cerr << "Not found '" << className << "'" << std::endl;
    return ANI_ERROR;
  }

  std::array methods = {
      ani_native_function{"walkMap", nullptr,
                          reinterpret_cast<void *>(ProcessWantPara)},
  };

  std::cout << "Start bind native methods to '" << className << "'"
            << std::endl;

  if (ANI_OK !=
      env->Class_BindNativeMethods(cls, methods.data(), methods.size())) {
    std::cerr << "Cannot bind native methods to '" << className << "'"
              << std::endl;
    return ANI_ERROR;
  };
  std::cout << "Finish bind native methods to '" << className << "'"
            << std::endl;

  *result = ANI_VERSION_1;
  return ANI_OK;
}
